/**
 * © 2009 Susanta Tewari
 * This file is part of genomemap.
 *
 * genomemap is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * genomemap is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with genomemap.  If not, see <http://www.gnu.org/licenses/>.
 */
package genomemap.data;

import genomemap.util.AbstractJob;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Manages assignments among genes and clones of a chromosome. Assignments are made using
 * data on the start and end base positions of genes and clones and complementation data that
 * specifies a direct tagging between genes and clones.
 * 
 * @see GWData
 * @see genomemap.map.PhysicalMap
 * @see genomemap.misc.MapFunction
 * @see genomemap.cli.CreateMaps
 *
 * @version 1.0 12/27/2009
 * @author Susanta tewari
 */
public class AssignmentManager extends AbstractJob {

    // <editor-fold defaultstate="collapsed" desc="Static Data">
    /**
     * Genome-wide assignment map of genes and clones with genes as keys. Assignment is done
     * either by region overlapping or complementation.
     */
    private static final Map<String, Set<String>> gwMap = GWData.getInstance().createMasterMap();

    /**
     * maintains a single instance per chromosome of a genome.
     */
    private static AssignmentManager[] instances = new AssignmentManager[7];// </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Instance Data">

    /**
     * <b>Chromosome</b> assignment map with genes as keys
     */
    final Map<String, Set<String>> g_map;

    /**
     * <b>Chromosome</b> assignment map with clones as keys
     */
    final Map<String, Set<String>> c_map;

    private int linkageGroup;
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Constructors">
    /**
     * Provides a cached instance of this class for the supplied linkage group. By default, this
     * uses hybridization to enrich the number of tagged probes. Note that this default instance
     * is used in Chromosome as a delegate for some methods.
     *
     * @param linkageGroup linkage group for the chromosome of the assignment manager
     * @return an cached instance of assignment manager for the given chromosome
     */
    public static synchronized AssignmentManager getInstance(int linkageGroup) {

        if (instances[linkageGroup - 1] == null) {

            AssignmentManager asm = new AssignmentManager(linkageGroup, true);
            instances[linkageGroup - 1] = asm;

        }

        return instances[linkageGroup - 1];
    }

    /**
     * Provides a non-cached instance of this class. An option of using hybridization for the
     * enrichment of assignments is available. By default, {@link #getInstance(int)} uses
     * hybridization.
     *
     * @param linkageGroup linkage group for the chromosome of the assignment manager
     * @param useHybridization if true, uses hybridization of clones to enrich the number
     * of probes in the assignment. By default, it is used as true.
     * 
     */
    public AssignmentManager(Integer linkageGroup, Boolean useHybridization) {

        this.linkageGroup = linkageGroup;

        Chromosome chr = Chromosome.getInstance(linkageGroup);

        // initialize and fill gene-keyed map. keep genes and clones only for the current
        // chromosome
        g_map = new HashMap<String, Set<String>>();

        for (String gene : gwMap.keySet()) {

            if (chr.getGenes().contains(gene)) {
                
                g_map.put(gene, new HashSet<String>());

                for (String clone : gwMap.get(gene)) {

                    if (chr.getClones().contains(clone))
                        g_map.get(gene).add(clone);
                }

            }

        }

        if (useHybridization) {

            for (String gene : g_map.keySet()) {

                Set<String> clones = g_map.get(gene);
                Set<String> connectingProbes = new HashSet<String>();

                for (String clone : clones) {
                    if (!chr.isProbe(clone)) {
                        Set<String> probes = chr.getHybridizingProbes(clone);
                        connectingProbes.addAll(probes);
                    }
                }

                // this helps in clearly seeing the added probes. Though could have been omitted,
                // by directly using cosmids.addAll(addedProbes).
                Set<String> addedProbes = new HashSet<String>();
                for (String val : connectingProbes) {
                    if (!clones.contains(val)) addedProbes.add(val);
                }

                clones.addAll(addedProbes);
            }

        }

        // initialize and fill clone-keyed map
        c_map = new HashMap<String, Set<String>>();
        for (String gene : g_map.keySet()) {
            for (String cosmid : g_map.get(gene)) {

                if (c_map.get(cosmid) == null) {
                    c_map.put(cosmid, new HashSet<String>());
                }

                c_map.get(cosmid).add(gene);
            }
        }
        

    }// </editor-fold>

    /**
     * Various methods for accessing tagged clones are available from Chromosome class. This
     * method should primarily be used for lower level tasks.
     * @return
     */
    public Map<String, Set<String>> getCloneAssignments() {
        return new HashMap<String, Set<String>>(c_map);
    }

    /**
     * Various methods for accessing tagged genes are available from <code>Chromosome</code> class.
     * This method should primarily be used for lower level tasks.
     * @return
     */
    public Map<String, Set<String>> getGeneAssignments() {
        return new HashMap<String, Set<String>>(g_map);
    }

    /**
     * Provides a modified genetic map using a physical map. 
     * .
     * @param physicalMap the physical map that modifies the genetic map
     * @param geneticMap the genetic map that is to be modified
     * @return an unmodifiable map of genes
     */
    public List<String> getModifiedGeneMap(final List<String> geneticMap, List<String> physicalMap) {

        return getModifiedMap(geneticMap, physicalMap, c_map);

    }

    /**
     * Provides a modified physical map using a genetic map.
     * @param geneticMap the genetic map that modifies the physical map
     * @param physicalMap the pysical map that is to be modified
     * @return an unmodifiable map of probes
     */
    public List<String> getModifiedProbeMap(final List<String> physicalMap, List<String> geneticMap) {

        return getModifiedMap(physicalMap, geneticMap, g_map);

    }

    /**
     * Map modification algorithm:
     * map --> (criterion) sorted subset --> (embed into the superset) modified map
     *
     * This is explained via an example. Using the assignments physical map
     * provides an ordering among a subset of genes in the genetic map. That order is then
     * embedded into the genetic map.
     * <p>
     * 
     * Say, the physical map is p_map = {P1, P2, P3} and the genetic map is
     * g_map = {G7, G1, G2, G3, G4, G5}. Let us assume the following assignments among genes
     * and probes.P1 -> {G1, G3, G6}; P2 -> {G1}; P3 -> {G2}
     * Then the modified genetic map would be {G7, G1, G3, G2, G4, G5}.
     *
     * @param byMap the map used in modification of the <code>fromMap</code>
     * @param fromMap the map to be modified
     * @param assigned_map c_map if byMap is physical and g_map if byMap is genetic
     * @return
     */
    private List<String> getModifiedMap(final List<String> fromMap, List<String> byMap,
            final Map<String, Set<String>> assigned_map) {

        // find the ordered elements
        List<String> ordered_elements = new ArrayList<String>();
        for (String by_map_element : byMap) {

            Set<String> assignees = assigned_map.get(by_map_element);

            if (assignees != null && assignees.size() > 0) {

                for (String assignee : assignees) {

                    if (!ordered_elements.contains(assignee)
                            && fromMap.contains(assignee)) {
                        ordered_elements.add(assignee);
                        break;
                    }
                }
            }

        }

        // embed the ordered elements
        List<String> modified_map = new ArrayList<String>();
        Iterator<String> itr = ordered_elements.iterator();

        for (int i = 0; i < fromMap.size(); i++) {
            if (ordered_elements.contains(fromMap.get(i))) {
                modified_map.add(itr.next());
            } else {
                modified_map.add(fromMap.get(i));
            }

        }

        return Collections.unmodifiableList(modified_map);
    }

}
